/**
 * @file   MiraCSIndexCont2D.h
 * @author Josep Sabater (jsabaterm@el.ub.edu)
 * @date   July, 2016
 * @version 1.0.0
 * @ingroup MiraUtilUBLib
 * @brief Indexed container 2D with constant size (runtime bound).
 * This is a constant time access container to store data.
 * Its size is determined when created and is constant along
 * its life.
 *
 */

#ifndef MIRA_CONST_SIZE_INDEX_CONT_2D_H
#define MIRA_CONST_SIZE_INDEX_CONT_2D_H

#include <cstddef>        // ptrdiff_t
#include <limits>         // std::numeric_limits 
#include <stdexcept>      // std::out_of_range
#include <memory>         // std::uninitialized_copy 
#include <new>            // std::bad_array_new_length 

#include "MiraCSIndexCont.h"

namespace MiraUtilUBLib{

template< class T >
    struct CSIndexCont2D{
        // types:
        typedef       T                               value_type;
        typedef       T&                              reference;
        typedef const T&                              const_reference;
        typedef       T*                              iterator;
        typedef const T*                              const_iterator;
        typedef std::reverse_iterator<iterator>       reverse_iterator;
        typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
        typedef size_t                                size_type;
        typedef ptrdiff_t                             difference_type;

        // fields:
    private:
        CSIndexCont<T> store;
        size_type nRow;
        size_type nCol;        

        // helper functions:
        size_type convert(size_type row, size_type col) {return col+(row*nCol); };
        void check(size_type rowcol) { store.check(rowcol); }
        void check(size_type row, size_type col) {store.check( convert(row, col) );}        

    public:
        // construct and destruct:
        CSIndexCont2D() = delete;
        const CSIndexCont2D operator=(const CSIndexCont2D&) = delete;

        explicit CSIndexCont2D(size_type row, size_type col)
            : store( CSIndexCont<T>(row*col) ), nRow(row), nCol(col){}
        
        explicit CSIndexCont2D(size_type row, size_type col, const T& td)
            : store( CSIndexCont<T>(row*col, td) ), nRow(row), nCol(col){}  

        CSIndexCont2D(const CSIndexCont2D& d)
            : store( CSIndexCont<T>(d.store) ), nRow(d.nRow), nCol(d.nCol){}

        ~CSIndexCont2D(){}

        // iterators:
        iterator       begin()        { return store.begin(); }
        const_iterator begin()  const { return store.begin(); }
        const_iterator cbegin() const { return store.cbegin(); }
        iterator       end()          { return store.end(); }
        const_iterator end()    const { return store.end(); }
        const_iterator cend()   const { return store.cend(); }

        reverse_iterator       rbegin()       
            { return reverse_iterator(end()); }
        const_reverse_iterator rbegin()  const
            { return reverse_iterator(end()); }
        reverse_iterator       rend()         
            { return reverse_iterator(begin()); }
        const_reverse_iterator rend()    const
            { return reverse_iterator(begin()); }

        // capacity:
        size_type size()     const { return store.size(); }
        size_type max_size() const { return store.size(); }
        bool      empty()    const { return store.size() == 0; }
        size_type rows()     const { return nRow; }
        size_type cols()     const { return nCol; }
        
        // element access:
        reference       front()       { return store.front(); }
        const_reference front() const { return store.front(); }
        reference       back()        { return store.back(); }
        const_reference back()  const { return store.back(); }

        const_reference operator()(size_type row, size_type col) const{
           return at(row, col);           
        }        
        reference operator()(size_type row, size_type col){
           return at(row, col);
        }                
        const_reference at(size_type row, size_type col) const { 
            size_type rowcol = convert(row,col);
            return store[rowcol]; 
        }
        reference       at(size_type row, size_type col) {
            size_type rowcol = convert(row,col);
            return store[rowcol];             
        }

        // data access:
        T*       data()       { return store.store; }
        const T* data() const { return store.store; }
    };

}
#endif /* MIRA_CONST_SIZE_INDEX_CONT_2D_H */

